/**
 * Written By: MinHo Kim (minho.kim.korea@gmail.com)
 * Copyright (C) 2011, MinHo Kim, Software Expert Program, KAIST University, Seoul, Korea.
 * This program is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU General Public License as published 
 * by the Free Software Foundation; version 2 of the License.
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
 * See the GNU General Public License for more details.
 */
package kaist.sep502.tsp.model;

import java.util.Map;

import kaist.sep502.tsp.util.TspUtil;

/**
 * @author minho kim
 *
 * 성능 향상을 위해서 static으로 처리한다.
 */
public class Problem {
	private static Map<Integer, City> cityMap;
	private static Distance[][] distances;
	private static int citySize;
	private static int departureCityNo;
	private static int goalCityNo;
	private static double meanPathCost;
	private static double maxPathCost;
	private static double minPathCost;
	
	private Problem(){
	}
	
	public static void init(Map<Integer, City> cityMap, int departureCityNo, int goalCityNo){
		Problem.cityMap = cityMap;
		Problem.citySize = (int) cityMap.size();
		Problem.departureCityNo = departureCityNo;
		Problem.goalCityNo = goalCityNo;
		
		// CityPath(link) 만들기
		initCityPath();
	}
	
	public static int getClosestCityNo(int fromCityNo, int[] exceptCityNo){
		int closestCityNo = -1;
		double shotestPathCost = Double.MAX_VALUE;
		
		boolean needExcept = false;
		
		for(int i=0; i< Problem.citySize; i++){
			needExcept = false;
			for(int j=0; j<exceptCityNo.length;j++){
				if(i == exceptCityNo[j]){
					needExcept = true;
					break;
				}
			}
			
			if(needExcept) {
				continue;
			}
			
			if(shotestPathCost > distances[fromCityNo][i].getDistance()){
				shotestPathCost = distances[fromCityNo][i].getDistance();
				closestCityNo = i;
			}
		}
		
		return closestCityNo;
	}
	
	public static int getClosestCityNoAdvanced(int fromCityNo, int[] exceptCityNo, int[] exceptCityNoSecond){
		int closestCityNo = -1;
		double shotestPathCost = Double.MAX_VALUE;
		int[] availableCities = getAvailableCities(exceptCityNo, exceptCityNoSecond);
		int size = availableCities.length;
		int toCityNo;
		double distance;
		
		for(int i=0; i< size; i++){
			toCityNo = availableCities[i];
			distance = distances[fromCityNo][toCityNo].getDistance();
			
			if(distance < shotestPathCost){
				shotestPathCost = distance;
				closestCityNo = toCityNo;
			}
		}
		
		return closestCityNo;
	}
	
	private static int[] getAvailableCities(int[] except1, int[] except2){
		int[] availableCities = new int[except1.length + except2.length];
		int index = 0;
		int citySize = Problem.getCitySize();
		boolean isExist = false;
		
		for(int i=0; i<citySize; i++){
			isExist = false;
			for(int j=0; j<except1.length;j++){
				if(i == except1[j]){
					isExist = true;
					break;
				}
			}
			if(isExist)	continue;
			
			isExist = false;
			for(int j=0; j<except2.length;j++){
				if(i == except2[j]){
					isExist = true;
					break;
				}
			}
			if(isExist)				continue;
			
			availableCities[index++] = i;
		}
		
		return availableCities;
	}
	
	public static int getClosestCityNoAdvanced1(int fromCityNo, int[] exceptCityNo, int[] exceptCityNoSecond){
		int closestCityNo = -1;
		double shotestPathCost = Double.MAX_VALUE;
		
		boolean needExcept = false;
		
		for(int i=0; i< Problem.citySize; i++){
			needExcept = false;
			for(int j=0; j<exceptCityNo.length;j++){
				if(i == exceptCityNo[j]){
					needExcept = true;
					break;
				}
			}
			
			if(needExcept) {
				continue;
			}
			
			needExcept = false;
			for(int j=0; j<exceptCityNoSecond.length;j++){
				if(i == exceptCityNoSecond[j]){
					needExcept = true;
					break;
				}
			}
			
			if(needExcept) {
				continue;
			}
			
			
			if(shotestPathCost > distances[fromCityNo][i].getDistance()){
				shotestPathCost = distances[fromCityNo][i].getDistance();
				closestCityNo = i;
			}
		}
		
		return closestCityNo;
	}
	
	private static void initCityPath(){
		double meanDistance = 0.0;
		double min = Double.MAX_VALUE;
		double max = 0.0;
		double dis = 0.0;
		int count = 0;
		
		Problem.distances = new Distance[Problem.citySize][Problem.citySize];
		for(int i=0; i<Problem.citySize; i++){
			for(int j=0; j<Problem.citySize; j++){
				if(i == j){ // 동일 도시의 거리는 무조건 0
					distances[i][j] = new Distance(i, j, 0, 0);
				} else {
					// TODO : 각도 만드는 로직 추가 필요 
					distances[i][j] = new Distance(i, j, 0, calculateDistance(getCity(i), getCity(j)));
					
					// mean, min, max values
					dis = distances[i][j].getDistance();
					meanDistance += dis;
					if(dis < min){
						min = dis;
					}
					if(max < dis){
						max = dis;
					}
					count++;
				}
			}
		}
		
		Problem.maxPathCost = TspUtil.formatDouble(max);
		Problem.minPathCost = TspUtil.formatDouble(min);
		Problem.meanPathCost = TspUtil.formatDouble(meanDistance/count);
		System.out.println("Distance - MEAN:" + (meanDistance/count) + " MAX:" + max + " MIN:" + min);
	}
	
	public static boolean checkGoal(CityPath cityPath) {
		if(cityPath.getPathCost() == 0){
			return false;
		}
		
		if(cityPath.index != Problem.getCitySize()+1){
			return false;
		}
		
		return true;
	}
	
	public static City getCity(int cityNo){
		return Problem.cityMap.get(cityNo);
	}
	
	public static double getDistance(int fromCityNo, int toCityNo){
		return Problem.distances[fromCityNo][toCityNo].getDistance();
	}
	
	private static double calculateDistance(City one, City two){
		double a = Math.abs(two.getX() - one.getX());
		double b = Math.abs(two.getY() - one.getY());
		return  Math.sqrt(a*a + b*b);
	}
	
	public static City getDeparture(){
		return cityMap.get(departureCityNo);
	}

	/**
	 * @return the cityMap
	 */
	public static Map<Integer, City> getCityMap() {
		return cityMap;
	}

	/**
	 * @return the cityPaths
	 */
	public static Distance[][] getCityPaths() {
		return distances;
	}

	/**
	 * @return the citySize
	 */
	public static int getCitySize() {
		return citySize;
	}

	/**
	 * @return the departureCityNo
	 */
	public static int getDepartureCityNo() {
		return departureCityNo;
	}

	/**
	 * @param departureCityNo the departureCityNo to set
	 */
	public static void setDepartureCityNo(int departureCityNo) {
		Problem.departureCityNo = departureCityNo;
	}

	/**
	 * @return the goalCityNo
	 */
	public static int getGoalCityNo() {
		return goalCityNo;
	}

	/**
	 * @param goalCityNo the goalCityNo to set
	 */
	public static void setGoalCityNo(int goalCityNo) {
		Problem.goalCityNo = goalCityNo;
	}

	/**
	 * @return the meanPathCost
	 */
	public static double getMeanPathCost() {
		return meanPathCost;
	}

	/**
	 * @return the maxPathCost
	 */
	public static double getMaxPathCost() {
		return maxPathCost;
	}

	/**
	 * @return the minPathCost
	 */
	public static double getMinPathCost() {
		return minPathCost;
	}
	
}
